package com.jijs.framework.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.log4j.Logger;

public class FileUtils {

	/** 日志对象 */
	private static final Logger LOG = Logger.getLogger(FileUtils.class);

	/**
	 * 缓存的webapp的目录
	 */
	private static String cachedWebAppBasePath = null;

	/**
	 * 清空一个目录，删除目录下的所有子文件及文件夹。但是不删除参数的目录。
	 * 
	 * @param forder
	 * @throws IOException
	 */
	public static void cleanForder(File forder) throws IOException {

		File[] files = forder.listFiles();

		IOException ioe = null;

		for (File file : files) {

			if (file.isDirectory()) {// 如果文件
				deleteForder(file);

			} else {

				// 我们使用尽量删除的方式，当删除一个文件失败，我们继续删除下一个文件
				try {
					deleteFile(file);
				} catch (IOException e) {
					LOG.error(e.getMessage(), e);
					ioe = e;
				}
			}
		}

		if (ioe != null) {
			throw ioe;
		}
	}

	/**
	 * 删除一个文件<br>
	 * 因为java.io.File#delete方法删除的时候只返回一个boolean值，很多时候影响我们的程序开发结果，因此我们
	 * 
	 * @param file
	 *            要被删除的文件
	 * @throws IOException
	 *             如果传入的不是一个文件(为文件夹)，则抛出异常。
	 * @throws IOException
	 *             如果传入的文件不存在，则抛出异常。
	 * @throws IOException
	 *             如果不能删除文件，同样抛出异常。
	 */
	public static void deleteFile(File file) throws IOException {

		// 这里不使用if(!file.isFile)而使用file.isDirectory()
		// 因为：当文件不存在的时候，file#isFile，file#isDirectory都返回false
		// 当被删除的文件不存在的时候，会被!file.isFile拦截，导致返回的消息为“要删除的不是一个文件”
		if (file.isDirectory()) {
			throw new IOException("要删除的不是一个文件，文件：" + file);
		}

		boolean isFileExists = file.exists();

		if (!file.delete()) {// 如果删除失败
			if (!isFileExists) {
				throw new IOException("要删除的文件不存在，文件：" + file);
			}
			throw new IOException("无法删除文件，文件：" + file);
		}
	}

	/**
	 * 删除一个文件夹以及文件夹下的所有文件及文件夹
	 * 
	 * @param forder
	 * @return
	 * @throws IOException
	 */
	public static void deleteForder(File forder) throws IOException {

		if (!forder.exists()) {
			return;
		}

		cleanForder(forder);// 清空目录

		if (!forder.delete()) {
			throw new IOException("无法删除文件夹，文件：" + forder);
		}
	}

	/**
	 * 获取文件大小
	 * 
	 * @param fileName
	 * @return
	 */
	public static String getFileSize(File fileName) {
		return getFileSize(fileName.length());
	}

	/**
	 * 获取文件大小
	 * 
	 * @param fileName
	 * @return
	 */
	public static String getFileSize(long fileSize) {

		String size;
		BigDecimal bd = new BigDecimal(fileSize);
		BigDecimal kbd = bd.divide(new BigDecimal(1024), 0, BigDecimal.ROUND_UP);

		if (kbd.compareTo(new BigDecimal(1024)) > 0) {
			BigDecimal mbd = kbd.divide(new BigDecimal(1024), 2, BigDecimal.ROUND_UP);
			size = mbd.toString() + "M";
		} else {
			size = kbd.toString() + "K";
		}

		return size;
	}

	/**
	 * 传入一个文件名，得到这个文件名称的后缀 包含 .
	 * 
	 * @param fileName
	 *            - 文件名
	 * @return
	 */
	public static String getSuffixContainPoint(String fileName) {

		int index = fileName.lastIndexOf(".");

		if (index != -1) {
			String suffix = fileName.substring(index);// 后缀
			return suffix;
		} else {
			return null;
		}
	}

	/**
	 * 传入一个文件名，得到这个文件名称的后缀 不包含 .
	 * 
	 * @param fileName
	 *            - 文件名
	 * @return
	 */
	public static String getSuffixUnContainPoint(String fileName) {

		int index = fileName.lastIndexOf(".");

		if (index != -1) {
			String suffix = fileName.substring(index + 1);// 后缀

			return suffix.toLowerCase();
		} else {

			return null;
		}
	}

	/**
	 * 获得项目的路径
	 * 
	 * @return 项目的路径
	 */
	public static String getWebAppBasePath() {

		if (cachedWebAppBasePath != null) {
			return cachedWebAppBasePath;
		}

		Class<FileUtils> cls = FileUtils.class;

		ClassLoader loader = cls.getClassLoader();
		// 获得类的全名，包括包名
		String clsName = cls.getName() + ".class";
		// 获得传入参数所在的包
		Package pack = cls.getPackage();
		String path = "";
		// 如果不是匿名包，将包名转化为路径
		if (pack != null) {
			String packName = pack.getName();
			// 此处简单判定是否是Java基础类库，防止用户传入JDK内置的类库
			if (!packName.startsWith("cn.com.infcn")) {
				throw new java.lang.IllegalArgumentException("类型不支持");
			}
			// 在类的名称中，去掉包名的部分，获得类的文件名
			clsName = clsName.substring(packName.length() + 1);
			// 判定包名是否是简单包名，如果是，则直接将包名转换为路径，
			if (packName.indexOf(".") < 0)
				path = packName + "/";
			else {// 否则按照包名的组成部分，将包名转换为路径
				int start = 0, end = 0;
				end = packName.indexOf(".");
				while (end != -1) {
					path = path + packName.substring(start, end) + "/";
					start = end + 1;
					end = packName.indexOf(".", start);
				}
				path = path + packName.substring(start) + "/";
			}
		}

		// 调用ClassLoader的getResource方法，传入包含路径信息的类文件名
		java.net.URL url = loader.getResource(path + clsName);
		// 从URL对象中获取路径信息

		String realPath = url.getPath();
		// 去掉路径信息中的协议名"file:"
		int pos = realPath.indexOf("file:");
		if (pos > -1)
			realPath = realPath.substring(pos + 5);

		// 去掉路径信息最后包含类文件信息的部分，得到类所在的路径
		pos = realPath.indexOf(path + clsName);
		realPath = realPath.substring(0, pos - 1);

		// 如果类文件被打包到JAR等文件中时，去掉对应的JAR等打包文件名
		boolean isRar = realPath.endsWith("!");
		if (isRar) {
			realPath = realPath.substring(0, realPath.lastIndexOf("/"));
		}

		// 结果字符串可能因平台默认编码不同而不同。因此，改用 decode(String,String) 方法指定编码。
		try {
			realPath = java.net.URLDecoder.decode(realPath, "utf-8");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		File file = new File(realPath);

		cachedWebAppBasePath = file.getParentFile().getParentFile().getAbsolutePath();

		return cachedWebAppBasePath;
	}

	/**
	 * 判断一个文件夹是否为另外一个文件夹的子文件夹
	 * 
	 * @param subFile
	 *            要判断的文件夹
	 * @param parentFile
	 *            是否存于的父文件夹
	 * @return 一个文件夹是否为另外一个文件夹的子文件夹。如果两个文件夹的路径相同，则返回false
	 */
	public static boolean isSubFile(File subFile, File parentFile) {

		File subParent = subFile.getParentFile();

		if (subParent == null) {// 不能获取上级文件夹的话，证明此节点已经是根文件夹，根文件夹不会是其它文件夹的子文件夹
			return false;
		}

		if (parentFile.equals(subParent)) {
			return true;
		}

		// 如果两个目录相同，表示已经处理完成了，不再继续判断
		if (subFile.equals(subParent)) {
			return false;
		}

		return isSubFile(subParent, parentFile);
	}

	/**
	 * 按照指定字符读取文件内容
	 * 
	 * @param file
	 * @param encoding
	 * @return
	 * @throws IOException
	 */
	public static byte[] readBytes(File file) throws IOException {

		if (file == null) {
			return null;
		}

		try (FileInputStream stream = new FileInputStream(file);
				ByteArrayOutputStream out = new ByteArrayOutputStream();) {

			byte[] b = new byte[1024 * 64]; // 缓存设置为64K
			int n;
			while ((n = stream.read(b)) != -1) {
				out.write(b, 0, n);
			}
			return out.toByteArray();
		}
	}

	/**
	 * 按照指定字符读取文件内容
	 * 
	 * @param file
	 * @param encoding
	 * @return
	 * @throws IOException
	 */
	public static String readString(File file, String encoding) throws IOException {
		return new String(readBytes(file), encoding);
	}

	/**
	 * 按照指定字符集将内容写入文件
	 * 
	 * @param content
	 *            文本内容
	 * @param file
	 *            文件
	 * @param encoding
	 *            字符集
	 * @param append
	 *            是否为追加写入
	 * @throws IOException
	 */
	public static void writeString(String content, File file, String encoding, boolean append) throws IOException {

		FileOutputStream fos = null;

		try {

			fos = new FileOutputStream(file, append);

			fos.write(content.getBytes(encoding));

		} catch (IOException e) {
			LOG.error(e.getMessage());
			throw new IOException();
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e1) {
					LOG.error(e1.getMessage());
				}
			}
		}

	}

	/**
	 * 按照指定字符集将内容写入文件
	 * 
	 * @param content
	 *            文本内容
	 * @param file
	 *            文件
	 * @param encoding
	 *            字符集
	 * @throws IOException
	 */
	public static void writeString(String content, File file, String encoding) throws IOException {
		writeString(content, file, encoding, false);
	}

	/**
	 * 子目录与根目录的相对路径
	 * 
	 * @param subFile
	 *            子目录
	 * @param rootFile
	 *            根目录
	 * @return 子目录与根目录的相对路径，如果子目录不再根目录下，返回null，如果子目录与根目录相同，返回空字符串。注意：返回值以文件分隔符开始。
	 */
	public static String subPath(File subFile, File rootFile) {

		if (subFile.equals(rootFile)) {
			return "";
		}

		// 如果目录并不是特定目录的子目录，那么无法执行后续的处理，因此返回空。
		if (!isSubFile(subFile, rootFile)) {
			return null;
		}

		String parentPath = rootFile.getAbsolutePath();
		String subPath = subFile.getAbsolutePath();

		return subPath.substring(parentPath.length());
	}

	/**
	 * 将文件夹的后面附加按照日期格式进行分割的子文件夹
	 * 
	 * @param forder
	 * @param patterns
	 * @return
	 */
	public static File withDate(File forder, String... patterns) {

		if (patterns == null || patterns.length == 0) {
			throw new IllegalArgumentException("参数patterns不能为空！");
		}

		File result = forder;

		Date date = new Date();
		for (String pattern : patterns) {
			SimpleDateFormat sdf = new SimpleDateFormat(pattern);
			result = new File(result, sdf.format(date));
		}

		return result;
	}

	/**
	 * 将文件夹的后面附加按照UUID分割后的格式进行分割的子文件夹
	 * <p>
	 * 因为uuid的长度为32，所以length*depth不能大于32。
	 * </p>
	 * <p>
	 * 例子：forder="/usr/ifc/";uuid=06A64D81680044AAB5D26D4F97C2D715。
	 * </p>
	 * <p>
	 * 如果length为2，depth为3的话，结果为/usr/ifc/06/A6/4D
	 * </p>
	 * <p>
	 * 如果length为3，depth为2的话，结果为/usr/ifc/06A/64D
	 * </p>
	 * 
	 * @param forder
	 *            文件夹
	 * @param uuid
	 *            唯一ID
	 * @return
	 */
	public static File withUUID(File forder, String uuid) {
		return withUUID(forder, uuid, 2, 3);
	}

	/**
	 * 将文件夹的后面附加按照UUID分割后的格式进行分割的子文件夹
	 * <p>
	 * 因为uuid的长度为32，所以length*depth不能大于32。
	 * </p>
	 * <p>
	 * 例子：forder="/usr/ifc/";uuid=06A64D81680044AAB5D26D4F97C2D715。
	 * </p>
	 * <p>
	 * 如果length为2，depth为3的话，结果为/usr/ifc/06/A6/4D
	 * </p>
	 * <p>
	 * 如果length为3，depth为2的话，结果为/usr/ifc/06A/64D
	 * </p>
	 * 
	 * @param forder
	 *            文件夹
	 * @param uuid
	 *            唯一ID
	 * @param length
	 *            每个文件夹的长度
	 * @param depth
	 *            深度
	 * @return
	 */
	public static File withUUID(File forder, String uuid, int length, int depth) {

		if (length * depth > uuid.length()) {
			throw new IllegalArgumentException("参数length与depth的乘积不能大于uuid的长度！");
		}

		File result = forder;

		int off = 0;

		for (int i = 0; i < depth; i++) {
			result = new File(result, uuid.substring(off, off + length));
			off += length;
		}

		return result;
	}
}